/* Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * openUBMC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
#define LUA_LIB

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <unistd.h>
#include <errno.h>
#include <lauxlib.h>
#include <lua.h>
#include <lualib.h>
#include <sys/stat.h>
#include <glib-2.0/glib.h>
#include <utils/vos.h>

#define MAX_DIRS 10
#define MAX_EVENTS 1024
#define EVENT_SIZE (sizeof(struct inotify_event))
#define BUF_LEN (MAX_EVENTS * (EVENT_SIZE + 16))

typedef struct {
    char **changes;
    int count;
    int capacity;
    int fd;     // inotify描述符
    int wd;     // 监视描述符
    int watching;
} ChangeList;

typedef struct {
    char *dir_path;     // 完整路径
    int watch_descriptor;
} WatchedDirectory;

WatchedDirectory watched_dirs[MAX_DIRS];
int dir_count = 0;

ChangeList *change_list;

static void l_init_change_list() {
    change_list = malloc(sizeof(ChangeList));
    if (!change_list) {
        perror("Failed to allocate memory for changelist");
        exit(EXIT_FAILURE);
    }
    change_list->changes = malloc(10 * sizeof(char*));
    if (!change_list->changes) {
        perror("Failed to allocate memory for changes");
        exit(EXIT_FAILURE);
    }
    change_list->count = 0;
    change_list->capacity = 10;
    change_list->watching = 0;
}

static void l_add_change(const char *path) {
    if (change_list->count >= change_list->capacity) {
        change_list->capacity *= 2;
        change_list->changes = realloc(change_list->changes, change_list->capacity * sizeof(char*));
        if (!change_list->changes) {
            perror("Failed to reallocate memory for changes");
            exit(EXIT_FAILURE);
        }
    }
    change_list->changes[change_list->count] = strdup(path);
    change_list->count++;
}

static void l_clear_changes() {
    for (int i = 0; i < change_list->count; i++) {
        free(change_list->changes[i]);
    }
    change_list->count = 0;
}

int l_get_changes(lua_State *L) {
    lua_newtable(L);
    for (int i = 0; i < change_list->count; i++) {
        lua_pushstring(L, change_list->changes[i]);     // 压入lua栈
        lua_seti(L, -2, i + 1);     // 设置未表元素
    }
    // 查询后清空
    change_list->count = 0;
    return 1;
}

static void l_stop_watching() {
    if (change_list->watching) {
        inotify_rm_watch(change_list->fd, change_list->wd);
        close(change_list->fd);
        change_list->watching = 0;
    }
}

static void l_watch_new_directory(const char *path) {
    change_list->wd = inotify_add_watch(change_list->fd, path, IN_MODIFY | IN_DELETE_SELF);
    if (change_list->wd < 0) {
        perror("inotify add watch err");
        return;
    }
    if (dir_count >= MAX_DIRS) {
        fprintf(stderr, "Max directory limit reached.\n");
        return;
    }
    watched_dirs[dir_count++] = (WatchedDirectory){.dir_path = strdup(path), .watch_descriptor = change_list->wd};
}

static void l_process_events() {
    char buf[BUF_LEN] = {0};
    struct inotify_event *event;
    while (change_list->watching) {
        int length = 0;
        int nread = 0;
        length = read(change_list->fd, buf, sizeof(buf) - 1);
        while(length > 0) {
            event = (struct inotify_event *)&buf[nread];
            char full_path[512];
            for (int i = 0; i < dir_count; i++) {
                if (event->wd == watched_dirs[i].watch_descriptor) {
                    if (event->len == 0) {
                        snprintf(full_path, sizeof(full_path), "%s", watched_dirs[i].dir_path);
                    } else {
                        snprintf(full_path, sizeof(full_path), "%s/%s", watched_dirs[i].dir_path, event->name);
                    }
                }
                break;
            }
            l_add_change(full_path);
            nread = nread + sizeof(struct inotify_event) + event->len;
            length = length - sizeof(struct inotify_event) - event->len;
        }
        usleep(300 * 1000);  // 避免事件风暴
    }
}

LOCAL gulong g_monitor_task_id = 0x00;

LOCAL void monitor_task(void)
{
    while(1) {
        l_process_events();
    }
}

static int l_watch_directory(lua_State *L) {
    const char *path = luaL_checkstring(L, 1);
    if (!change_list) {
        l_init_change_list();
        change_list->fd = inotify_init();
        if (!change_list->fd) {
            perror("inotify_init");
            return 0;
        }
        change_list->watching = 1;
    }
    // 添加新的监控
    l_watch_new_directory(path);
    vos_task_create(&g_monitor_task_id, "file_monitor_task", (TASK_ENTRY)monitor_task, (void *)NULL, DEFAULT_PRIORITY);
    return 0;
}

static int l_stop_and_clear() {
    l_stop_watching();
    l_clear_changes();
    for (int i = 0; i < dir_count; i++) {
        free(watched_dirs[i].dir_path);
    }
    return 0;
}


LUAMOD_API int luaopen_file_intf(lua_State *L)
{
    luaL_checkversion(L);
    luaL_Reg l[] = {
        // clang-format off
        {"watch_directory", l_watch_directory},
        {"stop_and_clear", l_stop_and_clear},
        {"get_changes", l_get_changes},
        {NULL, NULL}
        // clang-format on
    };
    luaL_newlib(L, l);

    return 1;
}